package com.lincoln.service;

import com.lincoln.entity.BaseIdEntity;
import com.lincoln.repository.BaseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * id Service的一些基本方法
 *
 * @param <ENTITY>
 * @param <REPOSITORY>
 * @author lincoln
 */
public abstract class BaseIdService<ENTITY extends BaseIdEntity, REPOSITORY extends BaseDao<ENTITY>> {
    @Autowired
    public REPOSITORY repository;

    /**
     * 通过Map中的参数查询分页
     *
     * @param pageRequest
     * @param params
     * @return
     */
    public Page<ENTITY> findPage(PageRequest pageRequest, Map<String, Object> params) {
        Specification<ENTITY> specification = new Specification<ENTITY>() {
            @Override
            public Predicate toPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return createPredicate(root, cb, params);
            }
        };
        Page<ENTITY> page = repository.findAll(specification, pageRequest);
        return page;
    }

    /**
     * 通过specification查询分页
     *
     * @param pageRequest
     * @param specification
     * @return
     */
    public Page<ENTITY> findPage(PageRequest pageRequest, Specification specification) {
        Page<ENTITY> page = repository.findAll(specification, pageRequest);
        return page;
    }

    /**
     * 通过specification查询列表
     *
     * @param specification
     * @return
     */
    public List<ENTITY> findList(Specification specification) {
        List<ENTITY> list = repository.findAll(specification);
        return list;
    }

    /**
     * 通过Map查询列表
     *
     * @param params
     * @return
     */
    public List<ENTITY> findList(Map<String, Object> params) {
        Specification<ENTITY> specification = new Specification<ENTITY>() {
            @Override
            public Predicate toPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return createPredicate(root, cb, params);
            }
        };
        List<ENTITY> list = repository.findAll(specification);
        return list;
    }

    /**
     * 通过键值对查找
     *
     * @param key
     * @param value
     * @return
     */
    public List<ENTITY> findByUnique(String key, Object value) {
        Specification<ENTITY> specification = new Specification<ENTITY>() {
            @Override
            public Predicate toPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Predicate result = cb.and();
                Predicate p = cb.equal(root.get(key).as(value.getClass()), value);
                result = cb.and(result, p);
                return result;
            }
        };
        List<ENTITY> list = repository.findAll(specification);
        return list;
    }

    /**
     * 通过键值对查找,并取出第一个值(用于非数据库主键的业务逻辑主键,比如用户名)
     *
     * @param key
     * @param value
     * @return
     */
    public ENTITY findByUniqueForOne(String key, Object value) {
        List<ENTITY> list = findByUnique(key, value);
        if (list == null || list.size() <= 0) {
            return null;
        } else {
            return list.get(0);
        }
    }

    /**
     * 保存实体类
     *
     * @param entity
     * @return
     */
    public ENTITY save(ENTITY entity) {
        return repository.save(entity);
    }

    /**
     * 删除实体类
     *
     * @param entity
     */
    public void delete(ENTITY entity) {
        repository.delete(entity);
    }

    /**
     * 删除实体类
     *
     * @param id
     */
    public void delete(long id) {
        repository.deleteById(id);
    }

    /**
     * 通过主键查询实体类
     *
     * @param id
     * @return
     */
    public ENTITY findById(long id) {
        try {
            return repository.findById(id).get();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 查询所有记录
     *
     * @return
     */
    public List<ENTITY> findAll() {
        return repository.findAll();
    }

    //======================================================一些工具方法============================================

    public static final String EQ_SUFFIX = "Eq";
    public static final String NE_SUFFIX = "Ne";
    public static final String LIKE_SUFFIX = "Like";
    public static final String NOTLIKE_SUFFIX = "NotLike";
    public static final String GT_SUFFIX = "Gt";
    public static final String GE_SUFFIX = "Ge";
    public static final String LT_SUFFIX = "Lt";
    public static final String LE_SUFFIX = "Le";


    /**
     * 构建筛选条件
     *
     * @param root
     * @param cb
     * @param params
     * @return
     */
    protected Predicate createPredicate(Root<ENTITY> root, CriteriaBuilder cb, Map<String, Object> params) {
        Class<ENTITY> clazz = (Class<ENTITY>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Predicate result = cb.and();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Class type = field.getType();
            String name = field.getName();
            if (params.get(name) != null) {
                Predicate p = cb.equal(root.get(name).as(params.get(name).getClass()), params.get(name));
                result = cb.and(result, p);
            }
            if (params.get(name + EQ_SUFFIX) != null) {
                Predicate p = cb.equal(root.get(name).as(params.get(name).getClass()), params.get(name + EQ_SUFFIX));
                result = cb.and(result, p);
            }
            if (params.get(name + NE_SUFFIX) != null) {
                Predicate p = cb.notEqual(root.get(name).as(params.get(name).getClass()), params.get(name + NE_SUFFIX));
                result = cb.and(result, p);
            }
            if (String.class.equals(type)) {
                if (params.get(name + LIKE_SUFFIX) != null) {
                    Predicate p = cb.like(root.get(name).as(String.class), "%" + params.get(name + LIKE_SUFFIX) + "%");
                    result = cb.and(result, p);
                }
                if (params.get(name + NOTLIKE_SUFFIX) != null) {
                    Predicate p = cb.like(root.get(name).as(String.class), "%" + params.get(name + NOTLIKE_SUFFIX) + "%");
                    result = cb.and(result, p);
                }
            }
            addNumberQuery(params, root, cb, result, type, name, "int", Integer.class);
            addNumberQuery(params, root, cb, result, type, name, "double", Double.class);
            addNumberQuery(params, root, cb, result, type, name, "short", Short.class);
            addNumberQuery(params, root, cb, result, type, name, "long", Long.class);
            addNumberQuery(params, root, cb, result, type, name, "float", Float.class);
            addNumberQuery(params, root, cb, result, type, name, "byte", Byte.class);
            if (Date.class.equals(type)) {
                if (params.get(name + GT_SUFFIX) != null) {
                    Predicate p = cb.greaterThan(root.get(name).as(Date.class), (Date) params.get(name + GT_SUFFIX));
                    result = cb.and(result, p);
                }
                if (params.get(name + GE_SUFFIX) != null) {
                    Predicate p = cb.greaterThanOrEqualTo(root.get(name).as(Date.class), (Date) params.get(name + GE_SUFFIX));
                    result = cb.and(result, p);
                }
                if (params.get(name + LT_SUFFIX) != null) {
                    Predicate p = cb.lessThan(root.get(name).as(Date.class), (Date) params.get(name + LT_SUFFIX));
                    result = cb.and(result, p);
                }
                if (params.get(name + LE_SUFFIX) != null) {
                    Predicate p = cb.lessThanOrEqualTo(root.get(name).as(Date.class), (Date) params.get(name + LE_SUFFIX));
                    result = cb.and(result, p);
                }
            }
        }
        return result;
    }

    /**
     * 抽出公共代码行,添加数字类型参数
     *
     * @param params
     * @param root
     * @param cb
     * @param result
     * @param type
     * @param name
     * @param compare
     * @param compareClazz
     */
    private void addNumberQuery(Map<String, Object> params, Root<ENTITY> root, CriteriaBuilder cb, Predicate result, Class type, String name, String compare, Class<? extends Number> compareClazz) {
        if (compare.equals(type.getName()) || compareClazz.equals(type)) {
            if (params.get(name + GT_SUFFIX) != null) {
                Predicate p = cb.gt(root.get(name).as(compareClazz), compareClazz.cast(params.get(name + GT_SUFFIX)));
                result = cb.and(result, p);
            }
            if (params.get(name + GE_SUFFIX) != null) {
                Predicate p = cb.ge(root.get(name).as(compareClazz), compareClazz.cast(params.get(name + GE_SUFFIX)));
                result = cb.and(result, p);
            }
            if (params.get(name + LT_SUFFIX) != null) {
                Predicate p = cb.lt(root.get(name).as(compareClazz), compareClazz.cast(params.get(name + LT_SUFFIX)));
                result = cb.and(result, p);
            }
            if (params.get(name + LE_SUFFIX) != null) {
                Predicate p = cb.le(root.get(name).as(compareClazz), compareClazz.cast(params.get(name + LE_SUFFIX)));
                result = cb.and(result, p);
            }
        }
    }

    /**
     * 构建一个分页信息
     *
     * @param pageNo   当前页码
     * @param pageSize 总页数
     * @param orders   排序方式
     * @return
     */
    public PageRequest buildPageRequest(int pageNo, int pageSize, Sort.Order... orders) {
        Sort sort = Sort.by(orders);
        return PageRequest.of(pageNo, pageSize, sort);
    }
}
